using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;

namespace Atomic.Libraries.Jobs
{
	/// <summary>
	/// Maintains the job.status file.
	/// </summary>
	[Serializable]
	internal sealed class JobStatus
	{
		private JobManager manager;
		private ReadOnlyDictionary<Job, JobStatusEntry> entries;
		private bool initializing;

		public JobStatus(JobManager manager)
		{
			Dictionary<Job, JobStatusEntry> entries = new Dictionary<Job, JobStatusEntry>();
			foreach (Job job in manager.Jobs)
			{
				entries.Add(job, new JobStatusEntry(this, job.Summary));
			}

			this.manager = manager;
			this.entries = new ReadOnlyDictionary<Job, JobStatusEntry>(entries);

			initializing = true;
		}

		public void Refresh()
		{
			using (FileLock fileLock = AcquireLock())
			{
				RefreshUnlocked(null);
			}
		}

		private void RefreshUnlocked(JobStatusEntry ignored)
		{
			using (StreamReader reader = new StreamReader(StatusFile.FullName))
			{
				// Ignore header line.
				reader.ReadLine();

				foreach (Job job in manager.Jobs)
				{
					string line = reader.ReadLine();
					if (entries[job] != ignored)
					{
						Parse(entries[job], line);
					}
				}
			}
		}

		public void Save()
		{
			// Done initializing when Save is called explicitly.
			initializing = false;

			using (FileLock fileLock = AcquireLock())
			{
				SaveUnlocked();
			}
		}

		public void Save(JobStatusEntry entry)
		{
			if (initializing)
			{
				// Don't want to save for each job submitted.
				return;
			}

			using (FileLock fileLock = AcquireLock())
			{
				if (entry != null)
				{
					RefreshUnlocked(entry);
				}

				SaveUnlocked();
			}
		}

		private void SaveUnlocked()
		{
			using (StreamWriter writer = new StreamWriter(StatusFile.FullName))
			{
				writer.WriteLine(Format("Job", "Id", "Start", "End", "State", "Summary"));
				foreach (Job job in manager.Jobs)
				{
					writer.WriteLine(Format(job));
				}
			}
		}

		private FileLock AcquireLock()
		{
			return FileLock.Acquire(StatusFile.AddFileExtension(".lock"));
		}

		private string Format(Job job)
		{
			JobStatusEntry entry = entries[job];

			string index = job.Index.ToString();
			if (job.Dependencies.Count > 0)
			{
				List<string> after = new List<string>();
				foreach (Job dependency in job.Dependencies)
				{
					after.Add(dependency.Index.ToString());
				}
				index += string.Format("({0})", string.Join(",", after));
			}

			return Format(index, entry.Id, FormatTime(entry.StartTime), FormatTime(entry.EndTime), entry.State.ToString(), Summary(entry));
		}

		private string Format(string job, string id, string start, string end, string status, string summary)
		{
			if (string.IsNullOrEmpty(id))
			{
				id = "-";
			}

			return string.Format("{0,-15} {1,-26} {2,-17} {3,-17} {4,-9} {5}", job, id, start, end, status, summary);
		}

		private string Summary(JobStatusEntry entry)
		{
			string s = entry.Summary.Replace("\r", "\n");

			// Truncate at first new line.
			if (s.Contains("\n"))
			{
				s = s.Substring(0, s.IndexOf('\n'));
			}

			return s;
		}

		private void Parse(JobStatusEntry entry, string line)
		{
			try
			{
				if (line == null)
				{
					Console.WriteLine("FIXME: line==null");
				}

				while (line.Contains("  "))
				{
					line = line.Replace("  ", " ");
				}
				string[] cols = line.Split(' ');

				try
				{
					entry.Id = cols[1];
				}
				catch
				{
					Console.WriteLine("FIXME: 1");
					throw;
				}
				try
				{
					entry.StartTime = ParseTime(cols[2]);
				}
				catch
				{
					Console.WriteLine("FIXME: 2");
					throw;
				}
				try
				{
					entry.EndTime = ParseTime(cols[3]);
				}
				catch
				{
					Console.WriteLine("FIXME: 3");
					throw;
				}
				try
				{
					entry.State = (JobState)Enum.Parse(typeof(JobState), cols[4]);
				}
				catch
				{
					Console.WriteLine("FIXME: 4");
					throw;
				}
			}
			catch
			{
				Console.WriteLine("FIXME:");
				Console.WriteLine(line);
				throw;
			}
		}
		
		private string FormatTime(DateTime? time)
		{
			if (time == null)
			{
				return "-";
			}

			// Using compact ISO 8601 format (T and Z are literal characters).
			return time.Value.ToString("yyyyMMdd\\THHmmss\\Z", CultureInfo.InvariantCulture);
		}

		private DateTime? ParseTime(string s)
		{
			if (s == "-")
			{
				return null;
			}

			return DateTime.ParseExact(s, "yyyyMMdd\\THHmmss\\Z", CultureInfo.InvariantCulture);
		}

		public ReadOnlyDictionary<Job, JobStatusEntry> Entries
		{
			get
			{
				return entries;
			}
		}

		public FileInfo StatusFile
		{
			get
			{
				return manager.OutputDirectory.AddFile("job.status");
			}
		}
	}
}
